import type { Metadata } from 'next';
import Link from 'next/link';
import { notFound } from 'next/navigation';
import { AdminTitle, Bool, JsonPre, Mono, StatusChip } from '@/components/admin/ui';
import { KeyValue } from '@/components/ui/key-value';
import { Unavailable } from '@/components/ui/unavailable';
import { adminApi, load, requireAdmin } from '@/lib/admin/admin-api';
import { fmtBytes, fmtDateTime, fmtInt } from '@/lib/format';
import { routes } from '@/lib/site';
export const metadata: Metadata = { title: 'Snapshot', robots: { index: false, follow: false } };
export const dynamic = 'force-dynamic';
/** Render the API's `diff` as change rows when it is a map/array of changes, otherwise as JSON. */
function Diff({ diff }: { diff: unknown }) {
if (diff === null || diff === undefined) return
No diff (first snapshot or unchanged).
;
const rows: { key: string; before: unknown; after: unknown }[] = [];
if (Array.isArray(diff)) {
for (const [i, it] of diff.entries()) {
if (it && typeof it === 'object') {
const o = it as Record;
rows.push({ key: String(o.key ?? o.path ?? o.property ?? o.field ?? i), before: o.old ?? o.before ?? o.from ?? o.old_value, after: o.new ?? o.after ?? o.to ?? o.new_value });
}
}
} else if (typeof diff === 'object') {
const o = diff as Record;
const looksLikeMap = Object.values(o).every((v) => v && typeof v === 'object' && !Array.isArray(v) && ('old' in (v as object) || 'new' in (v as object) || 'before' in (v as object) || 'after' in (v as object)));
if (looksLikeMap) {
for (const [k, v] of Object.entries(o)) {
const c = v as Record;
rows.push({ key: k, before: c.old ?? c.before, after: c.new ?? c.after });
}
}
}
if (!rows.length) return ;
const show = (v: unknown) => (v === undefined || v === null ? — : {typeof v === 'string' ? v : JSON.stringify(v)});
return (
{rows.map((r) => (
{r.key}{show(r.before)}{show(r.after)}
))}
);
}
export default async function AdminSnapshotPage({ params }: { params: Promise<{ id: string }> }) {
await requireAdmin();
const { id } = await params;
const res = await load(adminApi.snapshot(id));
if (!res.ok && res.error.startsWith('404')) notFound();
if (!res.ok) {
return (
<>
>
);
}
const s = res.data;
return (
<>
← Documents · document
{s.id}}>
Extraction debugger →
Structured extraction
Diff vs previous snapshot
Cleaned text excerpt {s.text_chars !== undefined && {fmtInt(s.text_chars)} chars{s.text_truncated ? ' · truncated at 20 kB' : ''}}
{s.text ? (
{s.text}
) : (
{s.text_error ? `Text unavailable: ${s.text_error}` : s.has_text === false ? 'No cleaned text for this snapshot (binary or JSON document).' : 'No text.'}